import type { Types } from '@cornerstonejs/core';
import {
  utilities,
  BaseVolumeViewport,
  StackViewport,
  cache,
  metaData,
} from '@cornerstonejs/core';
import type { Annotation } from '../types';
import { addAnnotation } from '../stateManagement/annotation/annotationState';
import { vec3 } from 'gl-matrix';

function annotationHydration(
  viewport: Types.IViewport,
  toolName: string,
  worldPoints: Types.Point3[],
  options?: {
    FrameOfReferenceUID?: string;
    annotationUID?: string;
  }
): Annotation {
  const viewReference = viewport.getViewReference();
  const { viewPlaneNormal, FrameOfReferenceUID } = viewReference;
  const annotation = {
    annotationUID: options?.annotationUID || utilities.uuidv4(),
    data: {
      handles: {
        points: worldPoints,
      },
    },
    highlighted: false,
    autoGenerated: false,
    invalidated: false,
    isLocked: false,
    isVisible: true,
    metadata: {
      toolName,
      viewPlaneNormal,
      FrameOfReferenceUID,
      referencedImageId: getReferencedImageId(
        viewport,
        worldPoints[0],
        viewPlaneNormal
      ),
      ...options,
    },
  };
  addAnnotation(annotation, viewport.element);
  return annotation;
}

function getReferencedImageId(
  viewport: Types.IViewport,
  worldPos: Types.Point3,
  viewPlaneNormal: Types.Point3
): string {
  let referencedImageId;

  if (viewport instanceof StackViewport) {
    referencedImageId = getClosestImageIdForStackViewport(
      viewport,
      worldPos,
      viewPlaneNormal
    );
  } else if (viewport instanceof BaseVolumeViewport) {
    const targetId = getTargetId(viewport);
    const volumeId = utilities.getVolumeId(targetId);
    const imageVolume = cache.getVolume(volumeId);

    referencedImageId = utilities.getClosestImageId(
      imageVolume,
      worldPos,
      viewPlaneNormal
    );
  } else {
    throw new Error(
      'getReferencedImageId: viewport must be a StackViewport or BaseVolumeViewport'
    );
  }

  return referencedImageId;
}

function getTargetId(viewport: Types.IViewport): string | undefined {
  const targetId = viewport.getViewReferenceId?.();
  if (targetId) {
    return targetId;
  }
  if (viewport instanceof BaseVolumeViewport) {
    return `volumeId:${getTargetVolumeId(viewport)}`;
  }
  throw new Error('getTargetId: viewport must have a getTargetId method');
}

function getTargetVolumeId(viewport: Types.IViewport): string | undefined {
  const actorEntries = viewport.getActors();

  if (!actorEntries) {
    return;
  }
  return actorEntries.find(
    (actorEntry) => actorEntry.actor.getClassName() === 'vtkVolume'
  )?.uid;
}

function getClosestImageIdForStackViewport(
  viewport: StackViewport,
  worldPos: Types.Point3,
  viewPlaneNormal: Types.Point3
): string {
  const imageIds = viewport.getImageIds();
  if (!imageIds || !imageIds.length) {
    return;
  }

  const distanceImagePairs = imageIds.map((imageId) => {
    const { imagePositionPatient } = metaData.get('imagePlaneModule', imageId);
    const distance = calculateDistanceToImage(
      worldPos,
      imagePositionPatient,
      viewPlaneNormal
    );
    return { imageId, distance };
  });

  distanceImagePairs.sort((a, b) => a.distance - b.distance);

  return distanceImagePairs[0].imageId;
}

function calculateDistanceToImage(
  worldPos: Types.Point3,
  ImagePositionPatient: Types.Point3,
  viewPlaneNormal: Types.Point3
): number {
  const dir = vec3.create();
  vec3.sub(dir, worldPos, ImagePositionPatient);

  const dot = vec3.dot(dir, viewPlaneNormal);

  return Math.abs(dot);
}
export { annotationHydration, getClosestImageIdForStackViewport };
